/**
 * Copyright [2010] Delta Air Lines, Inc. All Rights Reserved Access,
 * Modification, or Use Prohibited Without Express Permission of Delta Air
 * Lines.
 */
package com.delta.rafg.core.config;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.EventObject;
import java.util.HashMap;
import java.util.InvalidPropertiesFormatException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import com.delta.rafg.core.config.constants.ConfigSettingConstants;
import com.delta.rafg.core.database.DataHandlerFactory;
import com.delta.rafg.core.database.DataEvent;
import com.delta.rafg.core.database.IDataHandler;
import com.delta.rafg.core.database.IDataListener;
import com.delta.rafg.core.exception.RAFGException;

/**
 * ConfigSettings.java This class used to loading configuration properties file.
 * 
 * @author Delta
 * @version 1.0
 * @see Properties
 */
public final class ConfigSettings {

	/**
	 * Holds the value of serialVersionUID property.
	 */
	@SuppressWarnings("unused")
	private static final long serialVersionUID = 5934293253194456638L;

	private static final String[] configPropFiles = {
			"ConfigSettings.properties", "axissoa.properties",
			"soaendpoints.xml" };

	private static final String CONFIG_DB_TABLE = "CCMS_CACHE.CMSY_SVC";

	private static final String HOSTCONTEXT_CONFIG_SYNC = "AXS_CFG_HOSTCONTEXT.config.sync";

	private Properties allProps = null;

	private String SYSTEM_PROPERTIES[] = {
			ConfigSettingConstants.FULL_REFDATA_DOWNLOAD,
			ConfigSettingConstants.LOG_REMOTE_DEBUG };

	/**
	 * Defined as a system variable to be SystemTest, SystemIntegration, or
	 * Production
	 */
	public static final String SYSTEM_RAFG_ENVIRONMENT_KEY = "com.delta.rafg.core.config.environment";

	/**
	 * Defined as a system variable to contain the path to the config files
	 * local on this server. config files will first be looked for in this path
	 * before being looked for in the jar. DEVL =
	 * /dvl/axs/apps/was/i1/properties SI = /int/axs/apps/was/i1/properties PROD
	 * = /prd/axs/apps/was/i1/properties
	 */
	public static final String SYSTEM_RAFG_CONFIG_PATH_KEY = "com.delta.rafg.core.config.path";

	public static final String CONFIG_SETTINGS_DB_PREFIX = "com.delta.rafg.core.database.configsettings";

	private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
	private Lock readLock = rwl.readLock();
	private Lock writeLock = rwl.writeLock();

	public interface IConfigListener {
		public void configChanged(ConfigEvent configEvent);
	}

	public class ConfigEvent extends EventObject {
		private static final long serialVersionUID = 1637427057632915083L;
		private ConfigSettings _config;
		private Map<String, String> _changedConfigItems = new HashMap<String, String>();

		public ConfigEvent(Object source, ConfigSettings config,
				Map<String, String> changedConfigItems) {
			super(source);
			_config = config;
			_changedConfigItems = changedConfigItems;
		}

		public ConfigSettings getConfigSettings() {
			return _config;
		}

		public Map<String, String> getChangedItems() {
			return _changedConfigItems;
		}
	}

	private class MyDataListener implements IDataListener {

		@Override
		public void tableChangeEvent(DataEvent dataEvent) {
			Map<String, String> changedItems = null;
			try {
				Properties newProps = new Properties();
				loadAllPropertiesFromFile(newProps);
				changedItems = getAllPropsFromDb(newProps);
				loadSystemProperties(newProps);
				writeLock.lock();
				allProps = newProps;
			} catch (RAFGException e) {
				System.err
						.println(e
								+ "ConfigSettings.MyDataListener: Failure processing tableChangeEvent");
			} finally {
				writeLock.unlock();
			}
			if (changedItems != null) {
				notifyListeners(changedItems);
			}
		}

		@Override
		public String[] getMonitoredTables() {
			return new String[] { CONFIG_DB_TABLE };
		}
	}

	/**
	 * Holds the value of CONFIG_SETTINGS_DATAHANDLER_ID property.
	 */

	private List<IConfigListener> _listeners = new CopyOnWriteArrayList<IConfigListener>();

	IDataListener dbDataListener = new MyDataListener();

	/**
	 * Holds the value of configDataHandler property.
	 */
	private IDataHandler configDataHandler;

	/**
	 * Holds the value of configSettingsInstance property.
	 */
	private static ConfigSettings configSettingsInstance = null;

	private static Object lockObject = new Object();

	public static ConfigSettings getSingleton() throws RAFGException {
		return getSingleton(null);
	}

	/**
	 * @return a ConfigSettings singleton instance.
	 */
	public static ConfigSettings getSingleton(String inProp)
			throws RAFGException {
		if (configSettingsInstance == null) {
			// Author: 320800 - Start
			/**
			 * Moved the synchronization from the method level to inside so that
			 * it is used only the first time
			 */
			synchronized (lockObject) {
				if (configSettingsInstance == null) {
					String configSettingsSysProp = System
							.getProperty("ConfigSettings");
					if (configSettingsSysProp != null) {
						inProp = configSettingsSysProp;
					}
					if (inProp != null) {
						configSettingsInstance = newInstance(inProp);
					} else {
						configSettingsInstance = newInstance();
					}
				}
			}
			// Author: 320800 - End
		}

		return configSettingsInstance;
	}

	public static synchronized ConfigSettings newInstance()
			throws RAFGException {
		return new ConfigSettings();
	}

	/**
	 * @return new instance of ConfigSettings object
	 * @throws RAFGException
	 *             RAFGException
	 */
	public static synchronized ConfigSettings newInstance(String inProp)
			throws RAFGException {
		return new ConfigSettings(inProp);
	}

	private ConfigSettings() throws RAFGException {
		// set protected default properties
		allProps = new Properties();

		loadAllPropertiesFromFile(allProps);

		loadSystemProperties(allProps);
	}

	/**
	 * @throws RAFGException
	 *             RAFGException
	 * 
	 */
	private ConfigSettings(String inProp) throws RAFGException {
		// set protected default properties
		allProps = new Properties();

		// load any properties defined in a ConfigSettings.properties file.
		loadProperties(inProp, allProps);

		loadSystemProperties(allProps);
	}

	private void loadAllPropertiesFromFile(Properties newProps)
			throws RAFGException {
		for (String propFileName : configPropFiles) {
			loadProperties(propFileName, newProps);
		}
	}

	/**
	 * Load any system properties that Axis supports. This is typically used for
	 * overrides or custom/debug settings
	 * 
	 * @param newProps
	 */
	private void loadSystemProperties(Properties newProps) {

		for (String name : SYSTEM_PROPERTIES) {
			String value = System.getProperty(name);
			if (value != null) {
				newProps.setProperty(name, value);
			}
		}
	}

	/**
	 * @param inProp
	 * @throws RAFGException
	 */
	private void loadProperties(final String inProp, Properties newProps)
			throws RAFGException {
		if (inProp == null) {
			return;
		}
		try {
			InputStream inputStream = null;
			String environment = System
					.getProperty(ConfigSettings.SYSTEM_RAFG_ENVIRONMENT_KEY);
			String propFileName = null;

			// try to get an environment specific version of the properties/xml
			// file first.
			if (environment != null) {
				int indexLastPeriod = inProp.lastIndexOf(".");
				if (indexLastPeriod >= 0) {
					String suffix = inProp.substring(indexLastPeriod + 1);
					String prefix = inProp.substring(0, indexLastPeriod);
					propFileName = prefix + "." + environment + "." + suffix;
					inputStream = findPropFile(propFileName);
				}
			}

			// if that failed try again with just the property file name.
			if (inputStream == null) {
				inputStream = findPropFile(inProp);
			}

			if (inputStream == null) {
				throw new RAFGException(
						"Failed to find configuration properties file "
								+ inProp);
			}

			// load from properties if filename ends in .properties
			// load from XML if filename ends in anything else.
			if (inProp.trim().toLowerCase().endsWith(".properties")) {
				newProps.load(inputStream);
			} else {
				newProps.loadFromXML(inputStream);
			}
			inputStream.close();

		} catch (IOException e) {
			throw new RAFGException(e);
		}
	}

	@SuppressWarnings("resource")
	private InputStream findPropFile(final String inProp) {
		String localConfigPath = System
				.getProperty(ConfigSettings.SYSTEM_RAFG_CONFIG_PATH_KEY);
		InputStream inputStream = null;

		// First try to get file from local config path.
		try {
			if (localConfigPath != null && localConfigPath.length() >= 0) {
				inputStream = new FileInputStream(localConfigPath + "/"
						+ inProp);
			}
		} catch (IOException e) {
			// do nothing if this fails.
		}

		if (inputStream != null) {
			System.out.println("Loading properties from " + localConfigPath
					+ "/" + inProp);
		} else {
			// try to use a relative resource in the classpath.
			inputStream = getClass().getResourceAsStream(inProp);

			if (inputStream != null) {
				System.out.println("Loading properties from "
						+ getClass().getResource(inProp));
			} else {
				// try to get an actual filesystem path to the file, in case
				// this is in a local path (not in jar)
				try {
					inputStream = new FileInputStream(inProp);
					if (inputStream != null) {
						System.out.println("Loading properties from " + inProp);
					}
				} catch (IOException e) {
					// do nothing if this fails.
				}

			}
		}

		return inputStream;
	}

	public synchronized void initConfigDatabase() throws RAFGException {
		if (configDataHandler == null) {
			configDataHandler = DataHandlerFactory.getCacheDataHandler();

			// add myself as a listener and get all the props from the database
			// if configured to do so. (default is true...)
			if (Boolean
					.parseBoolean(getProperty(
							com.delta.rafg.core.database.SynchroHandler.CONFIG_SETTINGS_PREFIX
									+ ".enable", "true"))
					&& Boolean.parseBoolean(getProperty(
							CONFIG_SETTINGS_DB_PREFIX + ".enable", "true"))) {
				configDataHandler.addDataListener(dbDataListener);
			}
		}
		if (configDataHandler != null) {
			try {
				writeLock.lock();
				getAllPropsFromDb(allProps);
			} finally {
				writeLock.unlock();
			}
		}
	}

	/**
	 * Read all properties from the Db putting them in passed newProps param.
	 * Return a map of the properties that have changed.
	 * 
	 * @return properties that have changed.
	 * @throws RAFGException
	 */
	private Map<String, String> getAllPropsFromDb(Properties newProps)
			throws RAFGException {
		final Properties configDbProps = new Properties();
		Statement stmt = null;
		Map<String, String> changedItems = new HashMap<String, String>();

		try {
			final Connection conn = configDataHandler.getConnection();
			stmt = conn.createStatement();
			final ResultSet rs = stmt
					.executeQuery("SELECT CMSY_SVC_NM, CMSY_SVC_PTH_TXT FROM "
							+ CONFIG_DB_TABLE
							+ " WHERE CMSY_SVC_NM LIKE 'AXS_%' or CMSY_SVC_NM LIKE 'AXIS_%'  ");
			while (rs.next()) {
				final String name = rs.getString("CMSY_SVC_NM");
				String value = rs.getString("CMSY_SVC_PTH_TXT");
				if (name != null && name.length() > 0) {
					if (value == null) {
						value = "";
					}
					if (allowHostContextItem(name)) {
						String oldPropVal = getProperty(name);
						if (oldPropVal == null || !oldPropVal.equals(value)) {
							changedItems.put(name, value);
						}
						configDbProps.setProperty(name, value);
					}
				}
			}
		} catch (final SQLException e) {
			throw new RAFGException(e);
		} finally {
			if (stmt != null) {
				try {
					stmt.close();
				} catch (final SQLException e) {
					throw new RAFGException(e);
				}
			}
		}

		newProps.putAll(configDbProps);

		return changedItems;
	}

	private boolean allowHostContextItem(String name) {
		// if this is not a hostcontext item, return true
		if (!name.toUpperCase().startsWith("AXS_CFG_HOSTCONTEXT")) {
			return true;
		} else {
			// don't get SOA related if I am configured not to.
			return !getProperty(HOSTCONTEXT_CONFIG_SYNC, "true")
					.equalsIgnoreCase("false");
		}
	}

	private void notifyListeners(Map<String, String> changedItems) {
		ConfigEvent event = new ConfigEvent(this, this, changedItems);
		for (IConfigListener l : _listeners) {
			l.configChanged(event);
		}
	}

	public void addConfigListener(IConfigListener l) {
		synchronized (_listeners) {
			_listeners.add(l);
		}
	}

	public void removeConfigListener(IConfigListener l) {
		synchronized (_listeners) {
			_listeners.remove(l);
		}
	}

	public Map<String, String> convertToMap() {
		Set<Map.Entry<Object, Object>> es = entrySet();

		Map<String, String> map = new HashMap<String, String>();
		for (Map.Entry<Object, Object> entry : es) {
			map.put((String) entry.getKey(), (String) entry.getValue());
		}
		return map;
	}

	public Set<Map.Entry<Object, Object>> entrySet() {
		Set<Map.Entry<Object, Object>> retSet;
		readLock.lock();
		retSet = allProps.entrySet();
		readLock.unlock();
		return retSet;
	}

	@SuppressWarnings("unused")
	private void putAll(Properties map) {
		allProps.putAll(map);
	}

	@SuppressWarnings("unused")
	private void clear() {
		allProps.clear();
	}

	@SuppressWarnings("unused")
	private void load(InputStream inputStream) throws IOException {
		allProps.load(inputStream);
	}

	@SuppressWarnings("unused")
	private void loadFromXML(InputStream inputStream)
			throws InvalidPropertiesFormatException, IOException {
		allProps.loadFromXML(inputStream);
	}

	public String getProperty(String name, String defaultValue) {
		String retVal;
		readLock.lock();

		// Look for minor version override
		retVal = allProps.getProperty(name + "_" + getVersion(), defaultValue);

		// If not found look for major version override
		if (retVal == null || retVal == defaultValue
				|| retVal.equals(defaultValue))
			retVal = allProps.getProperty(name + "_" + getMajorVersion(),
					defaultValue);

		// If not found return default
		if (retVal == null || retVal == defaultValue
				|| retVal.equals(defaultValue))
			retVal = allProps.getProperty(name, defaultValue);

		readLock.unlock();
		return retVal;
	}

	public String getProperty(String name) {
		String retProp;
		readLock.lock();

		// Look for minor version override
		retProp = allProps.getProperty(name + "_" + getVersion());

		// If not found look for major version override
		if (retProp == null)
			retProp = allProps.getProperty(name + "_" + getMajorVersion());

		// If not found return default
		if (retProp == null)
			retProp = allProps.getProperty(name);

		readLock.unlock();
		return retProp;
	}

	public Object setProperty(String name, String value) {
		Object retProp;
		writeLock.lock();
		retProp = allProps.getProperty(name);
		allProps.setProperty(name, value);
		writeLock.unlock();
		return retProp;
	}

	private static String appVersion = null;
	private static String appMajorVersion = null;

	/**
	 * Either returns a ret built version like 1.5.1.20 or @build_version in dev
	 * mode.
	 * 
	 * @return
	 */
	public String getVersion() {
		if (appVersion == null) {
			try {
				Properties versioInfo = new Properties();
				try {
					versioInfo.load(ConfigSettings.class
							.getResourceAsStream("versioninfo.properties"));
				} catch (Exception e) {
					versioInfo.put("version", "@build_version");
				}

				appVersion = versioInfo.getProperty("version");

				if (appVersion == null || appVersion.isEmpty())
					appVersion = "@build_version";
			} catch (Throwable e) {
			}

		}
		return appVersion;
	}

	/**
	 * Either returns a ret built major version like 1.5 or @build_version in
	 * dev mode.
	 * 
	 * @return
	 */
	public String getMajorVersion() {
		if (appMajorVersion == null) {
			appMajorVersion = "@build_version";
			try {
				String theVersion = getVersion();
				if (theVersion != null && theVersion.indexOf(".") > 0) {

					String[] theVersionNumbers = theVersion.split("\\.");

					if (theVersionNumbers != null
							&& theVersionNumbers.length > 1) {
						appMajorVersion = theVersionNumbers[0] + "."
								+ theVersionNumbers[1];
					}
				}
			} catch (Throwable e) {
			}
		}
		return appMajorVersion;
	}

}
